﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using TimeTravel.Components;
using TimeTravel.Components.Enumeration;

namespace TimeTravel.Extensions
{
    /// <summary>
    /// 资源文件合并扩展
    /// </summary>
    public static class CombineResource
    {
        /// <summary>
        /// 合并资源文件路径 
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private static string CombinePath(ResourceInfo[] items)
        {
            if (!items.Any()) return String.Empty;
            //按优先级分组，控制优先级高的先输出
            var orderGroup = items.OrderBy(x => x.Order).GroupBy(x => x.Order).ToArray();
            var sb = new StringBuilder();
            foreach (var item in orderGroup)
            {
                var order = 1;
                var files = item.Select(x =>
                {
                    var lastIndex = x.Url.LastIndexOf('/');
                    var prefix = x.Url.Substring(0, lastIndex);
                    var fileName = x.Url.Substring(lastIndex + 1);
                    return new { Prfx = prefix, FileName = fileName, FileOrder = order++ };
                }).OrderBy(x => x.FileOrder);

                //按资源所属文件夹分组
                //假如有两个文件：A/a.js，A/b.js，属于同一个文件夹
                //则最终输出为：[A/a,b]
                var keysGroup = files.GroupBy(x => x.Prfx).ToArray();
                foreach (var key in keysGroup)
                {
                    var list = files.Where(x => x.Prfx.Equals(key.Key)).ToArray();
                    sb.Append("[" + list[0].Prfx + "/" + list[0].FileName);
                    for (var i = 1; i < list.Length; i++)
                    {
                        sb.Append("," + list[i].FileName);
                    }
                    sb.Append("]");
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// 请求参数拆分成为文件列表
        /// </summary>
        /// <param name="queryContent"></param>
        /// <returns></returns>
        private static string[] QueryToFileList(string queryContent)
        {
            var originQuery = queryContent.Replace("\\", "/").Replace(" ", "");
            var pathSegments = new List<string>();
            var filePathBuilder = new StringBuilder(50);
            var isCurrentCharInPathGroup = false;
            foreach (char currentChar in originQuery)
            {
                if (currentChar == '[')
                {
                    isCurrentCharInPathGroup = true;
                    filePathBuilder.Append(currentChar);
                    continue;
                }
                if (currentChar == ']')
                {
                    isCurrentCharInPathGroup = false;
                    filePathBuilder.Append(currentChar);
                    pathSegments.Add(filePathBuilder.ToString());
                    filePathBuilder.Clear();
                    continue;
                }
                if (isCurrentCharInPathGroup)
                {
                    filePathBuilder.Append(currentChar);
                    continue;
                }
                if (currentChar == ',')
                {
                    if (filePathBuilder.Length > 0)
                    {
                        pathSegments.Add(filePathBuilder.ToString());
                        filePathBuilder.Clear();
                    }
                    continue;
                }
                filePathBuilder.Append(currentChar);
            }
            if (filePathBuilder.Length > 0)
            {
                pathSegments.Add(filePathBuilder.ToString());
                filePathBuilder.Clear();
            }

            var paths = new List<string>();
            foreach (var pathSegment in pathSegments)
            {
                if (pathSegment.StartsWith("[") && pathSegment.EndsWith("]"))
                {
                    var clearedPathSegment = pathSegment.TrimStart('[').TrimEnd(']');
                    var splitterIndex = clearedPathSegment.LastIndexOf('/');
                    var pathPrefix = clearedPathSegment.Substring(0, splitterIndex + 1);
                    var pathContents = clearedPathSegment.Substring(splitterIndex + 1).Split(',');

                    paths.AddRange(pathContents.Select(pathContent => pathPrefix + pathContent));
                }
                else
                {
                    paths.Add(pathSegment);
                }
            }

            return paths.Distinct().ToArray();
        }

        /// <summary>
        /// 添加资源文件
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="resType">资源类型</param>
        /// <param name="url">文件路径</param>
        /// <param name="group">文件分组名称</param>
        /// <param name="order">文件同组中的优先级。默认：Normal</param>
        public static void AppendResFile(this HtmlHelper htmlHelper, ResourceType resType, string url, string group = "", PriorityType order = PriorityType.Normal)
        {
            var pathArray = QueryToFileList(url);
            AppendResFile(htmlHelper, resType, pathArray, group, order);
        }

        /// <summary>
        /// 添加资源文件
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="resType">资源类型</param>
        /// <param name="urls">文件的路径列表，如“channel/fanbuxie/urlcommon”,不支[]限定符</param>
        /// <param name="group">文件的分组名称</param>
        /// <param name="order">文件同组中的优先级,默认：Normal</param>
        private static void AppendResFile(this HtmlHelper htmlHelper, ResourceType resType, string[] urls, string group = "", PriorityType order = PriorityType.Normal)
        {
            Dictionary<string, ResourceInfo> resFiles;
            var urlArray = urls.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
            if (urlArray.Length == 0)
            {
                return;
            }
            var key = ResourceKey.ApendKey(resType);
            if (htmlHelper.ViewContext.HttpContext.Items.Contains(key))
            {
                resFiles = htmlHelper.ViewContext.HttpContext.Items[key] as Dictionary<string, ResourceInfo>;
            }
            else
            {
                resFiles = new Dictionary<string, ResourceInfo>();
                htmlHelper.ViewContext.HttpContext.Items.Add(key, resFiles);
            }
            foreach (var urlItem in urlArray)
            {
                if (resFiles.Keys.Contains(urlItem)) //如果已添加，则更新分组和优先级信息
                {
                    resFiles[urlItem].Group = @group;
                    resFiles[urlItem].Order = order;
                }
                else
                {
                    resFiles.Add(urlItem, new ResourceInfo { Url = urlItem, Group = @group, Order = order });
                }
            }
            htmlHelper.ViewContext.HttpContext.Items[key] = resFiles;
        }

        /// <summary>
        /// 移除资源文件
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="resType">资源类型</param>
        /// <param name="url">移除文件路径，可以为空或null</param>
        /// <param name="group">移除文件所在分组，可以为null</param>
        public static void RemoveResFile(this HtmlHelper htmlHelper, ResourceType resType, string url, string group = "")
        {
            var urlArray = QueryToFileList(url);
            RemoveResFile(htmlHelper, resType, urlArray, group);
        }

        /// <summary>
        /// 移除资源文件
        /// </summary>
        /// <param name="resType">资源类型</param>
        /// <param name="htmlHelper"></param>
        /// <param name="urls">移除文件列表,可以为空或则null </param>
        /// <param name="group">移除文件所在的组可以为null</param>
        public static void RemoveResFile(this HtmlHelper htmlHelper, ResourceType resType, string[] urls, string group = "")
        {
            var urlArray = urls.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();

            //按照地址移除
            if (urlArray.Length > 0)
            {
                List<string> removeFileKeys;
                var key = ResourceKey.RemoveKey(resType);

                if (htmlHelper.ViewContext.HttpContext.Items.Contains(key))
                {
                    removeFileKeys = htmlHelper.ViewContext.HttpContext.Items[key] as List<string>;
                }
                else
                {
                    removeFileKeys = new List<string>();
                    htmlHelper.ViewContext.HttpContext.Items.Add(key, removeFileKeys);
                }
                foreach (var urlItem in urlArray)
                {
                    var url = urlItem.Trim().ToLower();
                    if (!removeFileKeys.Contains(url))
                    {
                        removeFileKeys.Add(url);
                    }
                }
            }

            //按照group移除
            if (!string.IsNullOrEmpty(group))
            {
                List<string> removeGroupKeys;
                var keyGroup = ResourceKey.RemoveGroupKey(resType);
                if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyGroup))
                {
                    removeGroupKeys = htmlHelper.ViewContext.HttpContext.Items[keyGroup] as List<string>;
                }
                else
                {
                    removeGroupKeys = new List<string>();
                    htmlHelper.ViewContext.HttpContext.Items.Add(keyGroup, removeGroupKeys);
                }
                if (!removeGroupKeys.Contains(group))
                {
                    removeGroupKeys.Add(group);
                }
            }
        }

        /// <summary>
        /// 添加内嵌脚本或样式
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="resType"></param>
        /// <param name="script"></param>
        public static void AppendScriptBlock(this HtmlHelper htmlHelper, ResourceType resType, string script)
        {
            var key = ResourceKey.AppendScriptKey(resType);
            if (htmlHelper.ViewContext.HttpContext.Items.Contains(key))
            {
                var sb = htmlHelper.ViewContext.HttpContext.Items[key] as StringBuilder;
                sb.Append(script);
            }
            else
            {
                var sb = new StringBuilder();
                sb.Append(script);
                htmlHelper.ViewContext.HttpContext.Items[key] = sb;
            }
        }

        /// <summary>
        /// 输出合并后路径
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="resType">资源文件类型</param>
        /// <param name="ifCompress">是否压缩</param>
        /// <returns></returns>
        public static MvcHtmlString RenderResFile(this HtmlHelper htmlHelper, ResourceType resType, bool ifCompress = true)
        {
            var keyAppend = ResourceKey.ApendKey(resType);
            var keyRemove = ResourceKey.RemoveKey(resType);
            var keyRemoveGroup = ResourceKey.RemoveGroupKey(resType);
            var keyScriptBlock = ResourceKey.AppendScriptKey(resType);

            var content = new StringBuilder();
            if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyAppend))
            {
                var resFiles = htmlHelper.ViewContext.HttpContext.Items[keyAppend] as Dictionary<string, ResourceInfo>;
                //取出已标记的移除的资源文件
                var removeFileKey = new List<string>();
                if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyRemove))
                {
                    removeFileKey = htmlHelper.ViewContext.HttpContext.Items[keyRemove] as List<string>;
                }
                //取出已标记的移除的资源分组
                var removeGroupKey = new List<string>();
                if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyRemoveGroup))
                {
                    removeGroupKey = htmlHelper.ViewContext.HttpContext.Items[keyRemoveGroup] as List<string>;
                }
                //过滤资源文件（不包含标记为已移除的文件和分组）
                var files = resFiles.Select(x => x.Value)
                    .Where(x => !removeFileKey.Contains(x.Url) && !removeGroupKey.Contains(x.Group)).ToList();


                var jsGroupFiles = files.OrderByDescending(x => x.Group).GroupBy(x => x.Group);

                //按分组输出合并后的资源文件
                foreach (IGrouping<string, ResourceInfo> item in jsGroupFiles)
                {
                    var resPath = CombinePath(item.ToArray());
                    switch (resType)
                    {
                        case ResourceType.Script:
                            content.Append(string.Format("<script type=\"text/JavaScript\" src=\"/Resource/Script?href={0}{1}\"></script>", resPath,ifCompress?"&compress":""));
                            break;
                        case ResourceType.StyleSheet:
                            content.Append(string.Format("<link href=\"/Resource/Style?href={0}{1}\" type=\"text/css\" rel=\"stylesheet\" charset=\"utf-8\" />", resPath, ifCompress ? "&compress" : ""));
                            break;
                    }
                } 
            }
            //输出样式或者脚本文件代码块
            if (htmlHelper.ViewContext.HttpContext.Items.Contains(keyScriptBlock))
            {
                var script = htmlHelper.ViewContext.HttpContext.Items[keyScriptBlock] as StringBuilder;
                switch (resType)
                {
                    case ResourceType.Script:
                        content.Append(string.Format("<script type=\"text/JavaScript\">{0}</script>", script));
                        break;
                    case ResourceType.StyleSheet:
                        content.Append(string.Format("<style type=\"text/css\">{0}</style>", script));
                        break;
                }
            }
            RemoveResourceFromContext(htmlHelper,resType);
            return new MvcHtmlString(content.ToString());
        }

        /// <summary>
        /// 从当前ViewContext中移除添加的资源文件
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="resType"> </param>
        private static void RemoveResourceFromContext(HtmlHelper htmlHelper,ResourceType resType)
        {
            if (htmlHelper == null) return;
            htmlHelper.ViewContext.HttpContext.Items.Remove(ResourceKey.ApendKey(resType));
            htmlHelper.ViewContext.HttpContext.Items.Remove(ResourceKey.RemoveKey(resType));
            htmlHelper.ViewContext.HttpContext.Items.Remove(ResourceKey.RemoveGroupKey(resType));
            htmlHelper.ViewContext.HttpContext.Items.Remove(ResourceKey.AppendScriptKey(resType));
        }
        /// <summary>
        /// 资源添加键值管理
        /// </summary>
        private static class ResourceKey
        {
            private const string ConAppendFileKey = "AppendFileKey";
            private const string ConRemoveFileKey = "RemoveFileKey";
            private const string ConRemoveGroupKey = "RemoveGroupKey";
            private const string ConScriptBlockKey = "ScriptBlockKey";

            public static string ApendKey(ResourceType resType)
            {
                return string.Format("{0}_{1}", resType, ConAppendFileKey);
            }
            public static string RemoveKey(ResourceType resType)
            {
                return string.Format("{0}_{1}", resType, ConRemoveFileKey);
            }
            public static string RemoveGroupKey(ResourceType resType)
            {
                return string.Format("{0}_{1}", resType, ConRemoveGroupKey);
            }
            public static string AppendScriptKey(ResourceType resType)
            {
                return string.Format("{0}_{1}", resType, ConScriptBlockKey);
            }
        }
    }
}
